import axios, {
  AxiosInstance,
  AxiosRequestConfig,
  AxiosResponse,
  CreateAxiosDefaults,
  InternalAxiosRequestConfig
} from "axios"
import { useGlobalStore } from "@/store"
import { getRefreshToken } from "@/api"
import { checkStatus } from "./checkStatus"

const refreshTokenUrl = "/api/refresh-token"

export type Response<T> = Promise<[boolean, T, AxiosResponse<T>]>

class Request {
  constructor(config?: CreateAxiosDefaults) {
    this.axiosInstance = axios.create(config)

    this.axiosInstance.interceptors.request.use((axiosConfig: InternalAxiosRequestConfig) =>
      this.requestInterceptor(axiosConfig)
    )
    this.axiosInstance.interceptors.response.use(
      (response: AxiosResponse<unknown, unknown>) => this.responseSuccessInterceptor(response),
      (error: any) => this.responseErrorInterceptor(error)
    )
  }

  private readonly axiosInstance: AxiosInstance

  private refreshTokenFlag = false
  private requestQueue: {
    resolve: any
    config: any
    type: "reuqest" | "response"
  }[] = []
  private limit = 3

  private requestingCount = 0

  setLimit(limit: number) {
    this.limit = limit
  }

  private async requestInterceptor(axiosConfig: InternalAxiosRequestConfig): Promise<any> {
    if ([refreshTokenUrl].includes(axiosConfig.url || "")) {
      return Promise.resolve(axiosConfig)
    }

    if (this.refreshTokenFlag || this.requestingCount >= this.limit) {
      return new Promise((resolve) => {
        this.requestQueue.push({
          resolve,
          config: axiosConfig,
          type: "reuqest"
        })
      })
    }

    this.requestingCount += 1

    const { accessToken } = useGlobalStore.getState()

    if (accessToken) {
      axiosConfig.headers.Authorization = `Bearer ${accessToken}`
    }
    return Promise.resolve(axiosConfig)
  }

  private requestByQueue() {
    if (!this.requestQueue.length) return

    console.log(this.requestingCount, this.limit - this.requestingCount, "count")

    Array.from({ length: this.limit - this.requestingCount }).forEach(async () => {
      const record = this.requestQueue.shift()
      if (!record) {
        return
      }

      const { config, resolve, type } = record
      if (type === "response") {
        resolve(await this.request(config))
      } else if (type === "reuqest") {
        this.requestingCount += 1
        const { accessToken } = useGlobalStore.getState()
        config.headers.Authorization = `Bearer ${accessToken}`
        resolve(config)
      }
    })
  }

  private async refreshToken() {
    const { refreshToken } = useGlobalStore.getState()

    if (!refreshToken) {
      this.toLoginPage()
    }

    const [error, data] = await getRefreshToken(refreshToken)
    if (error) {
      this.toLoginPage()
    }

    useGlobalStore.setState({
      refreshToken: data?.result?.refreshToken,
      accessToken: data?.result?.accessToken
    })

    this.refreshTokenFlag = false

    this.requestByQueue()
  }

  private async responseSuccessInterceptor(response: AxiosResponse<any, any>): Promise<any> {
    if (response.config.url !== refreshTokenUrl) {
      this.requestingCount -= 1
      if (this.requestQueue.length) {
        this.requestByQueue()
      }
    }

    return Promise.resolve([false, response.data, response])
  }

  private async responseErrorInterceptor(error: any): Promise<any> {
    this.requestingCount -= 1
    const { config, status, message } = error?.response || {}

    if (status === 401) {
      return new Promise((resolve) => {
        this.requestQueue.unshift({ resolve, config, type: "response" })
        if (this.refreshTokenFlag) return
        this.refreshTokenFlag = true
        this.refreshToken()
      })
    } else {
      checkStatus(status, message).then(() => {})
      return Promise.resolve([true, error?.response?.data])
    }
  }

  private reset() {
    this.requestQueue = []
    this.refreshTokenFlag = false
    this.requestingCount = 0
  }

  private toLoginPage() {
    this.reset()
    // router.navigate('/user/login');
  }

  request<T, D = any>(config: AxiosRequestConfig<D>): Response<T> {
    return this.axiosInstance(config)
  }

  get<T, D = any>(url: string, config?: AxiosRequestConfig<D>): Response<T> {
    return this.axiosInstance.get(url, config)
  }

  post<T, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Response<T> {
    return this.axiosInstance.post(url, data, config)
  }

  put<T, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Response<T> {
    return this.axiosInstance.put(url, data, config)
  }

  delete<T, D = any>(url: string, config?: AxiosRequestConfig<D>): Response<T> {
    return this.axiosInstance.delete(url, config)
  }
}

const request = new Request({ timeout: 60 * 1000 * 5 })

export default request
